package com.bgyrobot.coordinate.model;

import java.util.regex.Pattern;

import com.alibaba.fastjson.annotation.JSONField;
import com.bgyrobot.coordinate.enumeration.CoordinateSystemTypeEnum;
import com.bgyrobot.coordinate.util.MathUtil;
import com.bgyrobot.coordinate.util.MatrixUtil;

import lombok.Data;

@Data
public class CoordinateSystem {
    
    public static final String GEOCS_NAME_PATTERN = "^(epsg|esri|nad27|nad83|world):.+$";
    public static final String LOCALCS_NAME_PATTERN = "^local:.+$";
    
    public static final boolean isEqual(CoordinateSystem cs1, CoordinateSystem cs2) {
        if (cs1 == null && cs2 == null) {
            return true;
        }
        if (cs1 == null || cs2 == null) {
            return false;
        }
        return cs1.type == cs2.type
                && cs1.baseCS.equalsIgnoreCase(cs2.baseCS)
                && MathUtil.arrayEqual(cs1.affineParams, cs2.affineParams, 1.0e-18)
                && MathUtil.arrayEqual(cs1.primitiveParams, cs2.primitiveParams, 1.0e-18)
                && MathUtil.arrayEqual(cs1.matrixParams, cs2.matrixParams, 1.0e-18);
    }
    
    public static final CoordinateSystem clone(CoordinateSystem cs) {
        if (cs == null) {
            return null;
        }
        CoordinateSystem cl = new CoordinateSystem();
        cl.baseCS = cs.baseCS;
        cl.type = cs.type;
        cl.affineParams = MathUtil.arrayClone(cs.affineParams);
        cl.primitiveParams = MathUtil.arrayClone(cs.primitiveParams);
        cl.matrixParams = MathUtil.arrayClone(cs.matrixParams);
        return cl;
    }
    
    /**
     * 矩阵名称
     */
    @JSONField(name="Name")
    private String name;
    
    /**
     * 矩阵类型
     */
    @JSONField(name="Type")
    private CoordinateSystemTypeEnum type;
    
    /**
     * 基础矩阵
     */
    @JSONField(name="BaseCS")
    private String baseCS;
    
    /**
     * 初级变换参数（平移=>旋转=>缩放）
     * [[xtrans, ytrans, ztrans], [yaw, pitch, roll], [xscale, yscale, zscale]]
     * 注：父坐标系坐标  要转为  当前坐标系下的坐标 时，要顺序进行: 平移 -> 旋转 -> 缩放，一共9个参数
     * 例：某局部坐标单位毫米，原点所在大地坐标为(730000, 2540000, 50)，y轴朝向为北偏东 0.25PI， 变换参数为 [[-730000, -2540000, -50],[0.25PI, 0, 0],[1000.0, 1000.0, 1000.0]]
     */
    @JSONField(name="PrimitiveParams")
    private double[][] primitiveParams;
    
    /**
     * 仿射变换参数（坐标对列表）
     * 
     * 2D-affine (base:baseCS this:thisCS):
     * [ [[base1X, base1Y], [base2X, base2Y], [base3X, base3Y]],
     *   [[this1X, this1Y], [this2X, this2Y], [this3X, this3Y]] ]
     *   
     * 3D-affine (base:baseCS this:thisCS):
     * [ [[base1X, base1Y, base1Z], [base2X, base2Y, base2Z], [base3X, base3Y, base3Z], [base4X, base4Y, base4Z]],
     *   [[this1X, this1Y, this1Z], [this2X, this2Y, this2Z], [this3X, this3Y, this3Z], [this4X, this4Y, this4Z]] ]
     */
    @JSONField(name="AffineParams")
    private double[][][] affineParams;
    
    /**
     * 矩阵变换参数（4X4 矩阵）
     */
    @JSONField(name="MatrixParams")
    private double[][] matrixParams;
    
    public CoordinateSystem() {
        this.name = "";
    }
    
    public CoordinateSystem(String csName) {
        this.name = csName;
    }
    
    public CoordinateSystem(String auth, String code) {
        this.name = String.format("%s:%s", auth, code);
    }
    
    @Override
    public String toString() {
        return name;
    }
    
    @Override
    public boolean equals(Object anObject) {
        return anObject instanceof CoordinateSystem && name.equalsIgnoreCase(anObject.toString());
    }
    
    @Override
    public int hashCode() {
        return name.hashCode();
    }
  
    /**
     * 计算局部坐标系的转换矩阵
     */
    public double[][] resolve() {
        try {
            if (type == CoordinateSystemTypeEnum.GEOGRAPHIC) {
                return null;
            }
            
            if (type == CoordinateSystemTypeEnum.MATRIX_RELATIVE) {
                return matrixParams;
            }
            
            if (type == CoordinateSystemTypeEnum.PRIMITIVE_RELATIVE) {
                if (primitiveParams == null) {
                    return null;
                }
                return MatrixUtil.primitiveMatrix(primitiveParams).getData();
            }
            
            if (type == CoordinateSystemTypeEnum.AFFINE_RELATIVE) {
                if (affineParams == null) {
                    return null;
                }
                return MatrixUtil.affineMatrix(affineParams, false).getData();
            }
        } catch (Exception ex) {
            return null;
        }
        
        return null;
    }
    
    /**
     * 检查原始的坐标系参数，返回异常错误信息，无异常则返回null
     */
    public String throwInvalidMessage() {
        
        if (name == null || name.equalsIgnoreCase("") || name.length() > 32) {
            return "name is null OR empty OR length>32";
        }
        
        if (type == null) {
            return "type is null";
        }
        
        if (type == CoordinateSystemTypeEnum.GEOGRAPHIC) {
            if (! Pattern.matches(CoordinateSystem.GEOCS_NAME_PATTERN, name)) {
                return "name of this type must starts with \"(epsg|esri|nad27|nad83|world):\"";
            }
            if (baseCS != null) {
                return "baseCS of this type must be null";
            }
            if (matrixParams != null) {
                return "matrixParams of this type must be null";
            }
            if (affineParams != null) {
                return "affineParams of this type must be null";
            }
            if (primitiveParams != null) {
                return "primitiveParams of this type must be null";
            }
        } else if (type == CoordinateSystemTypeEnum.AFFINE_RELATIVE) {
            if (! Pattern.matches(CoordinateSystem.LOCALCS_NAME_PATTERN, name)) {
                return "name of this type must starts with \"local:\"";
            }
            if (baseCS == null || baseCS.equalsIgnoreCase("") || baseCS.length() > 32) {
                return "baseCS of this type can't be null OR empty OR length>32";
            }
            if (matrixParams != null) {
                return "matrixParams of this type must be null";
            }
            if (affineParams == null ||
                    (! MathUtil.dataShape(affineParams).equalsIgnoreCase("2*3*2")
                  && ! MathUtil.dataShape(affineParams).equalsIgnoreCase("2*4*3"))) {
                return "affineParams of this type must be real-array shape of 2*3*2 OR 2*4*3";
            }
            if (primitiveParams != null) {
                return "primitiveParams of this type must be null";
            }
        } else if (type == CoordinateSystemTypeEnum.PRIMITIVE_RELATIVE) {
            if (! Pattern.matches(CoordinateSystem.LOCALCS_NAME_PATTERN, name)) {
                return "name of this type must starts with \"local:\"";
            }
            if (baseCS == null || baseCS.equalsIgnoreCase("") || baseCS.length() > 32) {
                return "baseCS of this type can't be null OR empty OR length>32";
            }
            if (matrixParams != null) {
                return "matrixParams of this type must be null";
            }
            if (affineParams != null) {
                return "affineParams of this type must be null";
            }
            if (primitiveParams == null || ! MathUtil.dataShape(primitiveParams).equalsIgnoreCase("3*3")) {
                return "primitiveParams of this type must be real-array shape of 3*3";
            }
        } else if (type == CoordinateSystemTypeEnum.MATRIX_RELATIVE) {
            if (! Pattern.matches(CoordinateSystem.LOCALCS_NAME_PATTERN, name)) {
                return "name of this type must starts with \"local:\"";
            }
            if (baseCS == null || baseCS.equalsIgnoreCase("") || baseCS.length() > 32) {
                return "baseCS of this type can't be null OR empty OR length>32";
            }
            if (matrixParams == null || ! MathUtil.dataShape(matrixParams).equalsIgnoreCase("4*4")) {
                return "matrixParams of this type must be real-array shape of 4*4";
            }
            if (affineParams != null) {
                return "affineParams of this type must be null";
            }
            if (primitiveParams != null) {
                return "primitiveParams of this type must be null";
            }
        } else {
            return "invalid Coordinate-System Type";
        }
        return null;
        
    }
  
}
